/* 
 * File:   TUClient.cpp
 * Author: arschmitz
 *
 * Created on April 3, 2011, 12:09 PM
 */

#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <mhash.h>
#include <sys/wait.h>
#include <typeinfo>
#include <curl/curl.h>
#include "../hdrs/list.h"
#include "TigerUpdate.h"
#define HASH_ALGO MHASH_MD5
#define HASH_BUFF 1024

/***************************\
*  TUClient - Program Flow  *
\***************************/

using namespace std;

TUClient::TUClient() {
    //Read local settings (usually hardcoded)
    getSettings();
    //Create a bidirectional socket system, from the hardcoded settings
    //Send a packet to the server with settings & info
    //First, send an announce message, with the recvPort info.
    char port[5];
    sprintf(port, "%d", myRecvSock->getAddress()->getPort());
    TUObject *message = new TUMessage(0x00, 0x00, port);
    TUObject *expects = new TUMessage(0x01, 0x00, "Server Response OK");
    //Wait for response
    if(!sendAndWait(message, expects)) {
        cerr << "ERR: Failed to connect to server. Aborting..." << endl;
        exit(1);
    }

    //Ask for the config file
    message = new TUMessage(0x00, 0x10, "Asking for config file");
    expects = new TUFile(); //Will be replaced with the sent file
    //Wait for response
    if(!sendAndWait(message, expects)) {
        cerr << "WARN: Config file expected. Got something else." << endl;
        //TODO: Add logic instead of exiting immediately. Could be a message to
        //      wait a bit, or to contact another server, or whatever.
        exit(1);
    }
    //If if gets this far, "expects" is now the sent config file.
    //Parse the config file
    if(!parseConfig((TUFile *)expects)) {
        cerr << "ERR: Config file parsing did not complete.  Exiting..." << endl;
        exit(1);
    }

    //If security structure is present, do security stuff (To be determined later)
    //If control structure is present, do what it states, restart TU
    //If data structure is present, continue, else ERR
    //If unique code is present, save it for later.

    //NOTE: If we get here, it must be a config file
    TUFile *file = (TUFile *) expects;
    //For each line in data structure
    char *line, fileName[40], fileHash[mhash_get_block_size(HASH_ALGO)];
    unsigned int filesNeedingUpdating = 0;
    list_t *listOfFilenames = createList();

    while(file->hasNext()){
        line = file->getNext();
        // Open the specified file
        sscanf(line, "%39s, %s", fileName, fileHash);
        
        // Hash it using MD5 (benchmarks show this is the fastest hash)
        // Compare result to file
        if(hashFile(fileName, fileHash) != 0) { //Incorrect
            // If it's incorrect, add the line to a linked list
            addObject(listOfFilenames, fileName);
            addObject(listOfFilenames, fileHash);
        } // Else, do nothing
    } // Loop to the next line in the config file

    resetList(listOfFilenames);
    cerr <<  "NOTE: " << filesNeedingUpdating << " file(s) need updating." << endl;

    CURL *curl = curl_easy_init();
    if(curl == NULL) {
        cerr << "ERR: Failed to initialize downloader.  Exiting..." << endl;
        exit(1);
    }
    //At this time, only files needing updates are still within the file
    //For each line in data structure
    for(int i=0; i<filesNeedingUpdating; i++) {
        // Send a packet to the server, containing unique code (if provided)
        // Wait for the file to arrive

        strncpy(fileName, (char *) deleteObject(listOfFilenames, 0), 40);
        strncpy(fileHash, (char *) deleteObject(listOfFilenames, 0),
                mhash_get_block_size(HASH_ALGO));
        FILE *overwritenFile = fopen(fileName, "r");
        if(overwritenFile == NULL) {
            cerr << "ERR: Failed to open local file: " << fileName << "  Exiting..." << endl;
            exit(1);
        }
        char dlFileLocation[strlen(myDlerAddr)+strlen(fileName)];
        strcat(dlFileLocation, myDlerAddr);
        strcat(dlFileLocation, fileName);
        //= myDlerAddr+fileName;
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, overwritenFile);
        curl_easy_setopt(curl, CURLOPT_URL, dlFileLocation);
        curl_easy_perform(curl);
        curl_easy_reset(curl);
        
    //    If transmit is OK, replace old file with newly received one.
    //    Hash it using MD5

    //    Compare result to file
       if(!hashFile(fileName, fileHash)){
           addObject(listOfFilenames, fileName);
           addObject(listOfFilenames, fileHash);
       }
    //    If same, remove that line, else, keep it & continue
    //    **NOTE**  This does have a chance for an infinite loop
    }
    //Close connection to the server
    curl_easy_cleanup(curl);
    //End Tiger Update successfully
    cerr << "NOTE: TigerUpdate successfully updated your program." << endl;
    //Continue execution of client program
}

TUClient::~TUClient() {
    delete this;
}

int TUClient::getSettings() {
    TUAddress *srvrAddr = new TUAddress("server.arschmitz.com",53197);
    TUAddress *recvAddr = new TUAddress(ALL_ADDRESSES, 53199);
    mySendSock = new TUSocket(srvrAddr);
    myRecvSock = new TUSocket(recvAddr);
    myRecvSock->bindMe();
    myDlerAddr = "server.arschmitz.com/tu/";
    return 1;
}

int TUClient::sendAndWait(TUObject *send, TUObject *waitFor) {
    int pid = fork();
    if(!pid){ // If Child
        TUObject *buffer = new TUObject(NULL);
        TUAddress sender;
        myRecvSock->recvMe(buffer, &sender);
        if(waitFor != buffer){
            cerr << "WARN: Correct message type was not received." << endl;
            exit(1);
        } else {
            *waitFor = buffer; // Overwrite waitFor with the newly sent object
            exit(0);
        }
    } else { // End child code
        //TODO: Add repetition and a timeout
        mySendSock->sendMe(send);
        int status;
        wait(&status); // Wait for child to complete
        return !status; // 1 if OK, 0 if not OK
    }
}

int TUClient::hashFile(const char *filename, char *hash) {
    FILE *currentFile = fopen(filename, "r");
    if(currentFile == NULL) {
        cerr << "ERR: Failed to open local file: " << filename << "  Exiting..." << endl;
        exit(1);
    }
    MHASH currentHash = mhash_init(HASH_ALGO);
    char buffer[HASH_BUFF];
    char result[mhash_get_block_size(HASH_ALGO)];

    if (currentHash == MHASH_FAILED) {
        cerr << "ERR: MHASH Failed to initialize on file: " <<
                filename << "  Exiting..." << endl;
        exit(1);
    }

    while (fread(&buffer, sizeof(char), HASH_BUFF, currentFile) == HASH_BUFF) {
        mhash(currentHash, &buffer, HASH_BUFF);
    }

    mhash_deinit(currentHash, result);
    if(hash != NULL){
        return(strcmp(hash, result) == 0);
    } else {
        hash = result;
        return(1);
    }
}

bool TUClient::parseConfig(TUFile* configFile, char expectedVersion){
    char *data = (char *) configFile->getFileData();
    if(data[0] != expectedVersion){
        cerr << "ERR: Config file Version was expected to be " <<
            expectedVersion << ", but it's " << data[0] << " instead" << endl;
    }
    switch((int) data[0]) {
        case 0x01: return parseConfigV1(data);
        default: cerr << "ERR: Config file profile is not loaded.  Exiting..." << endl; exit(1);
    }
    return(false); // Should Never get here!
}

bool TUClient::parseConfigV1(char* data){
/*****  Format of Version 1 Config Files ************************************\
 * byte - data    - Notes:                                                  *
 *    0 - version - Better be '0x01' if we got here                         *
 *    1 - srvLen  - Length of the server address string                     *
 *    2 - rcvLen  - Length of the accepted connections string               *
 *    3 - dlrLen  - Length of the downloader address string                 *
 *  4-5 - srvPort - The port of the server                                  *
 *  6-7 - rcvPort - The local port to receive data through                  *
 *   *1 - srvAdd  - Server address, either an IP address or URL is OK       *
 *   *2 - rcvAdd  - Accepts connections from, IP address or URL is OK       *
 *   *3 - dlrAdd  - Downloader address, either an IP address or URL is OK   *
\****************************************************************************/
    // Read the data in
    assert(data[0] == 0x01);
    char srvLen = data[1];
    char rcvLen = data[2];
    char dlrLen = data[3];
    int srvPort = data[4] << 8 + data[5];
    int rcvPort = data[6] << 8 + data[7];
    char srvAdd[srvLen], rcvAdd[rcvLen], dlrAdd[dlrLen];
    strncpy(srvAdd, data + 8, srvLen);
    strncpy(rcvAdd, data + 8 + srvLen, rcvLen);
    strncpy(dlrAdd, data + 8 + srvLen + rcvLen, dlrLen);
    // Assign data to correct fields
    TUAddress *srvrAddr = new TUAddress("server.arschmitz.com",srvPort);
    TUAddress *recvAddr = new TUAddress(ALL_ADDRESSES, rcvPort);
    mySendSock = new TUSocket(srvrAddr);
    myRecvSock = new TUSocket(recvAddr);
    myRecvSock->bindMe();
    myDlerAddr = "server.arschmitz.com/tu/";
    return 1;
}